package jp.co.menox.tmserver.dao;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.slim3.datastore.DaoBase;
import org.slim3.datastore.Datastore;
import org.slim3.datastore.EntityNotFoundRuntimeException;
import org.slim3.datastore.ModelQuery;

import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Query.SortDirection;
import com.google.appengine.api.datastore.Transaction;

import jp.co.menox.tmcommon.meta.CommonModelMeta;
import jp.co.menox.tmcommon.model.CommonModelBase;

/**
 * サーバチーム用Dao基底クラス<br />
 * テーブル共通要素に基づく基本的なデータアクセスを記述
 * @author 
 *
 * @param <T> modelクラス
 */
public class TmServerDaoBase<T extends CommonModelBase> extends DaoBase<T>{

    private String nameOfId;
    private String nameOfWasDeleted;
    private String nameOfCreated;
    private String nameOfModified;

    {
        CommonModelMeta meta = CommonModelMeta.get();
        nameOfId = meta.id.getName();
        nameOfWasDeleted = meta.wasDeleted.getName();
        nameOfCreated = meta.created.getName();
        nameOfModified = meta.modified.getName();
    }

    

    
    // **** Query用メソッド
    
    /**
     * 引数のqueryに対して有効な（論理削除されていない）レコードを取得する条件を追加する
     * @param query
     * @return
     */
    public ModelQuery<T> getValidQuery(ModelQuery<T> query) {
        query = query
                .filter(nameOfWasDeleted, Query.FilterOperator.EQUAL, false);
        return query;
    }

    
    // **** getAll用メソッド

    /*
     * TODO: 
     * 検索条件付きの取得／ページングによる取得との整合性を要検討。
     */

    /**
     * 有効な（論理削除されていない）全レコードを取得する
     * @return
     */
    public List<T> getAll() {
        return getAll(false);
    }
    
    /**
     * 全レコードを取得する。論理削除されたレコードを含む。
     * @return
     */
    public List<T> getAllWithDeleted() {
        return getAll(true);
    }
    
    /**
     * 全レコードを取得する
     * @param hasDeleted 削除されたレコードを含むか否か
     * @return
     */
    protected List<T> getAll(boolean hasDeleted) {
        
        ModelQuery<T> query = query();
        
        if (!hasDeleted) {
            // 無効項目を含まない
            query = query.filter(nameOfWasDeleted, Query.FilterOperator.EQUAL, false);
        }
        
        query = query.sort(nameOfWasDeleted, SortDirection.DESCENDING);
        
        List<T> result = query.asList();
        if (result == null) {
            result = new ArrayList<T>();
        }

        return result;
    }
    
    
    // **** イベント処理用メソッド

    /**
     * selectイベント用
     * @param model
     */
    protected void onSelected(T model){
    }

    /**
     * insertイベント用
     * @param key
     */
    protected void onInserted(Key key){
    }

    /**
     * updateイベント用
     * @param key
     */
    protected void onUpdated(Key key){
    }

    /**
     * deleteイベント用
     * @param key
     */
    protected void onDeleted(Key key){
    }

    /**
     * vanishイベント用
     * @param key
     */
    protected void onVanished(Key key){
    }


    // **** CROUD用ラップ

    /**
     * 取得：　keyに基づく
     * @param key
     * @return
     */
    public T selectBg(Key key) {
        if (key == null) {
            return null;
        }
        T existModel = null;
        try {
            existModel = get(key);
        } catch(Exception e) {
            if (e.getClass().equals(EntityNotFoundRuntimeException.class)) {
                return null;
            }
            throw e;
        }
        return existModel;
    }

    /**
     * event: 取得：　keyに基づく
     * @param key
     * @return
     */
    public T select(Key key) {
        T existModel = selectBg(key);
        if (existModel == null) {
            return existModel;
        }
        onSelected(existModel);
        return existModel;
    }

    /**
     * 取得：　idに基づく
     * @param id
     * @return
     */
    public T selectBg(long id) {
        T existModel = query()
                .filter(nameOfId, Query.FilterOperator.EQUAL, id)
                .asSingle();
        return existModel;
    }

    /**
     * event: 取得：　idに基づく
     * @param id
     * @return
     */
    public T select(long id) {
        T existModel = selectBg(id);
        if (existModel == null) {
            return existModel;
        }
        onSelected(existModel);
        return existModel;
    }

    /**
     * 新規追加
     * @param model
     * @return
     */
    protected Key insertBg(T model) {
        if (model == null) {
            return null;
        }
        Key key = Datastore.allocateId(m);
        return this.insertBg(key, model);
    }

    /**
     * event: 新規追加
     * @param model
     * @return
     */
    public Key insert(T model) {
        Key key = insertBg(model);
        if (key == null) {
            return key;
        }
        onInserted(key);
        return key;
    }

    /**
     * 新規追加：　key指定（主に関連付け用）<br />
     * TODOF: 今回entity groupを使用しないため廃止扱い
     * @param key
     * @param model
     * @return
     */
    @Deprecated
    protected Key insertBg(Key key, T model) {
        if (key == null) {
            return null;
        }
        if (model == null) {
            return null;
        }
        model.setKey(key);
        model.setId(key.getId());
        model.setWasDeleted(false);
        model.setCreated(new Date());
        model.setModified(null);
        key = put(model);
        return key;
    }

    /**
     * event: 新規追加：　key指定（主に関連付け用）<br />
     * TODOF: 今回entity groupを使用しないため廃止扱い
     * @param key
     * @param model
     * @return
     */
    @Deprecated
    public Key insert(Key key, T model) {
        key = insertBg(key, model);
        if (key == null) {
            return key;
        }
        onInserted(key);
        return key;
    }

    /**
     * 更新：　keyに基づく（事前に取得したmodelの項目値を編集した引数の前提）
     * @param model
     * @return
     */
    protected Key updateBg(T model) {
        if (model == null) {
            return null;
        }
        
        Transaction tx = Datastore.beginTransaction();
        Key key = null;
        try {
            // 排他チェック
            T existModel = Datastore.get(m, model.getKey(), model.getVersion());

            // テーブル共通部は変更させない
            model.setId(existModel.getId());
            model.setWasDeleted(existModel.getWasDeleted());
            model.setCreated(existModel.getCreated());
            model.setModified(new Date());
            
            key = put(tx, model);

            tx.commit();
        } catch(Exception e) {
            if (tx.isActive()) {
                tx.rollback();
            }
            throw e;
        }

        return key;
    }

    /**
     * event : 更新：　keyに基づく（事前に取得したmodelの項目値を編集した引数の前提）
     * @param model
     * @return
     */
    public Key update(T model) {
        Key key = updateBg(model);
        if (key == null) {
            return key;
        }
        onUpdated(key);
        return key;
    }

    /**
     * 更新：　idに基づく
     * @param id
     * @param model
     * @return
     */
    protected Key updateBg(long id, T model) {
        if (model == null) {
            return null;
        }
        
        T existModel = selectBg(id);
        if (existModel == null) {
            return null;
        }

        Transaction tx = Datastore.beginTransaction();
        Key key = null;
        try {
            // 排他チェック
            Datastore.get(m, existModel.getKey(), existModel.getVersion());

            // 管理項目設定
            model.setKey(existModel.getKey());
            model.setVersion(existModel.getVersion());

            // テーブル共通部は変更させない
            model.setId(existModel.getId());
            model.setWasDeleted(existModel.getWasDeleted());
            model.setCreated(existModel.getCreated());
            model.setModified(new Date());

            key = put(tx, model);

            tx.commit();
        } catch(Exception e) {
            if (tx.isActive()) {
                tx.rollback();
            }
            throw e;
        }

        return key;
    }

    /**
     * event: 更新：　idに基づく
     * @param id
     * @param model
     * @return
     */
    public Key update(long id, T model) {
        Key key = updateBg(id, model);
        if (key == null) {
            return key;
        }
        onUpdated(key);
        return key;
    }

    /**
     * 論理削除：　keyに基づく（事前に取得したmodelの項目値を編集した引数の前提）
     * @param model
     * @return
     */
    protected Key deleteBg(T model) {
        if (model == null) {
            return null;
        }

        Transaction tx = Datastore.beginTransaction();
        Key key = null;
        try {
            // 排他チェック
            T existModel = Datastore.get(m, model.getKey(), model.getVersion());
            if (existModel == null) {
                // ここにはこない：　先にEntityNotFoundRuntimeExceptionになる
                throw new EntityNotFoundRuntimeException(model.getKey());
            }

            // テーブル共通部（論理削除）を更新
            existModel.setWasDeleted(true);
            existModel.setModified(new Date());
            
            key = put(tx, existModel);

            tx.commit();
        } catch(Exception e) {
            if (tx.isActive()) {
                tx.rollback();
            }
            throw e;
        }

        return key;
    }

    /**
     * event: 論理削除：　keyに基づく（事前に取得したmodelの項目値を編集した引数の前提）
     * @param model
     * @return
     */
    public Key delete(T model) {
        Key key = deleteBg(model);
        if (key == null) {
            return key;
        }
        onDeleted(key);
        return key;
    }

    /**
     * 論理削除：　idに基づく
     * @param id
     * @return
     */
    protected Key deleteBg(long id) {
        
        T existModel = selectBg(id);
        if (existModel == null) {
            return null;
        }

        Transaction tx = Datastore.beginTransaction();
        Key key = null;
        try {
            // 排他チェック
            Datastore.get(m, existModel.getKey(), existModel.getVersion());

            // テーブル共通部（論理削除）を更新
            existModel.setWasDeleted(true);
            existModel.setModified(new Date());

            key = put(tx, existModel);

            tx.commit();
        } catch(Exception e) {
            if (tx.isActive()) {
                tx.rollback();
            }
            throw e;
        }

        return key;
    }

    /**
     * event: 論理削除：　idに基づく
     * @param id
     * @return
     */
    public Key delete(long id) {
        Key key = deleteBg(id);
        if (key == null) {
            return key;
        }
        onDeleted(key);
        return key;
    }

    /**
     * 物理削除：　keyに基づく（事前に取得したmodelの項目値を編集した引数の前提）
     * @param model
     * @return
     */
    protected Key vanishBg(T model) {
        if (model == null) {
            return null;
        }
        Key key = model.getKey();
        delete(key);
        return key;
    }

    /**
     * event: 物理削除：　keyに基づく（事前に取得したmodelの項目値を編集した引数の前提）
     * @param model
     * @return
     */
    public Key vanish(T model) {
        Key key = vanishBg(model);
        if (key == null) {
            return key;
        }
        onVanished(key);
        return key;
    }

    /**
     * 物理削除：　idに基づく
     * @param id
     * @return
     */
    protected Key vanishBg(long id) {
        T existModel = selectBg(id);
        return vanishBg(existModel);
    }

    /**
     * event: 物理削除：　idに基づく
     * @param id
     * @return
     */
    public Key vanish(long id) {
        Key key = vanishBg(id);
        if (key == null) {
            return key;
        }
        onVanished(key);
        return key;
    }


    // **** transaction用ラップ

    // **** TODOF: transactionは根本的に概念が違うので今回は適当

    public T get(Transaction tx, Key key) {
        return Datastore.get(tx, m, key);
    }

    public Key put(Transaction tx, T model) {
        return Datastore.put(tx, model);
    }

}
